package com.text
{
	import flash.display.DisplayObject;
	import flash.display.Sprite;
	import flash.events.ContextMenuEvent;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.events.TimerEvent;
	import flash.text.TextField;
	import flash.ui.ContextMenu;
	import flash.ui.ContextMenuItem;
	import flash.ui.Keyboard;
	import flash.utils.Dictionary;
	import flash.utils.Timer;
	
	import mx.controls.TextArea;
	import mx.events.ResizeEvent;
	import mx.events.ScrollEvent;
 
	public class CustomTextArea extends TextArea
	{
	  	private var hSpelling:Highlighter;//spelling highlighter
        private var hAdded:Highlighter;//additions highlighter
        private var hDeleted:Highlighter; //deletions highlighter
		
		//private var originalFormat:TextFormat ; 
			
		/**Tracking current selection range and type */		
		private var currentSelection:Range;
		private var currentSelectionType:int= NONE;
		private const NONE:int = -1;
		private const SPELLING:int = 0;
		private const ADD:int = 1;
		private const DELETE:int = 2;
		
		/** Event Type fired so all highlighters listening start drawing their highlightes */
		public static const DRAW_EVENT:String = "DRAW";  
		/** Event Type fired causing all highlighters to relocat the positions of their highlightes - causes a DRAW_EVENT to be fired eventually */
	    public static const REFRESH_EVENT:String = "REFRESH";
	    /** Event Type fired when user ignoreall a word, the ignoredwordslist of this control should be used to update all controls that need to do the same ignore**/
	    public static const IGNORE_WORDS_EVENT:String = "IgnoredWordsChanged";
	    
	    /** A timer to redraw the highlights connected to scrolling and resizing*/
        private var validateTimer:Timer;
  		private var isInitialized:Boolean =false;
	 	/**
        * The drawing canvas for the Bitamp object. This is what all highlighters will use to draw their highlights
        * if a changes occurs that need to clear the highlights , highlighters will respond by redrawing what they have.
        */
        public var highlightsCanvas:Sprite;
        
        /**indicates that the highlights are invalid and require a redraw**/ 
        private var highlightsInvalid:Boolean = false;
        
        /**indicates that the highlights location have changed and they need to be located beffore a redraw **/ 
        private var highlightsLocationInvalid:Boolean = false;
         
		public function CustomTextArea()
		{
			//trace ("CustomTextArea: Created");
			 super();
			 this.selectable = true; 
			 this.condenseWhite = false;
			 this.addEventListener(MouseEvent.CLICK,mouseClick_Handler,false,1,true);
			 this.addEventListener(Event.CHANGE,resize_Handler,false,1,true);
			 this.addEventListener(KeyboardEvent.KEY_DOWN,resize_Handler,false,1,true); 
		} 
			 
		private var _autoSize : Boolean = true; 
 		public function set autoSize(value : Boolean) : void
		{
			_autoSize = value;
			invalidateSize();
		} 
		public function get autoSize() : Boolean
		{
			return _autoSize;
		} 
		
		/** total text change clear all highlighes and ranges */
		public override function set text(value:String):void
		{    
			//trace ("CustomTextArea: text property set, clearing highlights");
			//clear ranges and words set for the previous text
			setCurrentSelection(new Range(0,0));   
 		 	if (hSpelling != null)
		 	{	 hSpelling.reset();
		 		 hSpelling = null;
		 	}
		 	if (hAdded != null) 
		 	{
		 		hAdded.reset();
			hAdded = null;
			}
			if (hDeleted != null) 
		 	{
		 		hDeleted.reset();
				hDeleted = null;
			}
 			super.text = value; 
			invalidateHighlights(); 
			invalidateSize();
		}
		/**Internal Array words that should be ignored*/
		private static var ignoredWords:Array = new Array();
		/**flag for commitproperties that ignorelist has changed*/
		private var _ignoredWordsChanged:Boolean; 
		 
		
		private var _wrongWords:Object;
		private var _wrongWordsChanged:Boolean;//wrong word dictionary changed
		/**dictionary of words that are to be marked as wrong.  The key is the word and the value is an array of suggesstions*/
		public function get wrongWords():Object
		{
		 return _wrongWords
		} 
		public function set wrongWords(value:Object):void
		{
		  _wrongWords= value;
		  _wrongWordsChanged = true;
		 invalidateHighlights();
		}
		
		private var _addedIndexRanges:Array;
		private var _addedChanged:Boolean;//additions array changed 
		/**array of Range  that are to be marked as added content. **/
		public function get  addedIndexRanges():Array
		{   if (hAdded != null)
			{ 
			    _addedIndexRanges = hAdded.ranges;
			} 
			if (_addedIndexRanges ==null)
			{ 
				 _addedIndexRanges = new Array();
			}
			
		    return _addedIndexRanges
		} 
		public function set addedIndexRanges(value:Array):void
		{
		   // trace ("CustomTextArea: addedIndexRanges property set ");
		  _addedIndexRanges= value;
		  _addedChanged = true;
		  invalidateHighlights();
		}
	
		private var _deletedIndexRanges:Array;
		private var _deletedChanged:Boolean;//deletions array  changed
		/**array of Range  that are to be marked as deleted content. **/
		public function get  deletedIndexRanges():Array
		{
		   if (hDeleted != null)
		   { 
			  _deletedIndexRanges = hDeleted.ranges;
		   } 
		   if (_deletedIndexRanges ==null )
		   {
		   	  _deletedIndexRanges = new Array();
		   }
		   return _deletedIndexRanges
		} 
		public function set deletedIndexRanges(value:Array):void
		{
			//trace ("CustomTextArea: deletedIndexRanges property set ");
		  _deletedIndexRanges= value;
		  _deletedChanged=true;
		  invalidateHighlights();
		} 
		   
        //apply the properties that changed 
		protected override function commitProperties():void
		{
			super.commitProperties();
 			/* if (originalFormat==null) 
				originalFormat = this.textField.defaultTextFormat;
			else
			    this.textField.setTextFormat(originalFormat); */
			//apply list of wrong words     
			if (_wrongWordsChanged || _ignoredWordsChanged)
			{      
				_wrongWordsChanged = false;
		  		_ignoredWordsChanged = false;
				 if (_wrongWords!=null )
				 {
				 	var wordsToHighlight:Array = new Array(); 
			   		//read words from dictionary and build the array
				  	for (var key:Object in _wrongWords)
				  	{
					  	if (CustomTextArea.ignoredWords.indexOf(key) == -1 ) //word is not ignored
					   		wordsToHighlight.push(key); 
					}  
					if (wordsToHighlight.length >0)
					{
					 	 if (hSpelling == null) 
					 		  hSpelling = new Highlighter(this,this.textField as TextField,new CustomTextFormat(false,0xff000000,false,0xff000000,true,0xffff0000),"spelling");
	            	 	 else	
				  			 hSpelling.reset();
				  	 	 setupContext();
				  	     hSpelling.highlightWords(wordsToHighlight,false);
					}
				}
				//if spelling was highlihgted and there is no words now to highlighted drop the spelling
				if (hSpelling != null && (wordsToHighlight== null || wordsToHighlight.length==0) )
				{	 //hSpelling not needed anymore
				  	hSpelling.reset();
				  	destroyContext();
				  	hSpelling == null; 
				} 
								
			}
			
			//apply list of deleted changes 
			if (_deletedChanged)
			{
				if (_deletedIndexRanges!=null && _deletedIndexRanges.length > 0)  
			  	{
			  		if (hDeleted == null) 
			  		{
				   		hDeleted  =new Highlighter(this,this.textField as TextField,new CustomTextFormat(false,0xff00ffff,true,0xffff0000,false,0xff000000),"deleted");
            	 		//AH corrng the text is causing probs when delting all text in the field.
            	 		//Need to find a way to attach the coloring to the link  this.hDeleted.textFormat.color= 0xffff0000;
              		}
              		else
			    		hDeleted.reset();
			    	hDeleted.highlightRanges(_deletedIndexRanges);
			    }		 
              	else if (hDeleted != null)
		  		{	//hDeleted is not needed anymore
		  			hDeleted.reset();
		  			hDeleted =null;
		  		}  
			    _deletedChanged = false;
				
			} 
			//apply list of added changes
			if (_addedChanged)
			{
				
				if (_addedIndexRanges!=null && _addedIndexRanges.length > 0 )
				{ 
					if (hAdded == null) 
						hAdded =  new Highlighter(this,this.textField as TextField,new CustomTextFormat(true,0xff00ffff,false,0xff000000,false,0xff000000),"added");
					else	
						hAdded.reset();
					hAdded.highlightRanges(_addedIndexRanges);
				}
				else if (hAdded != null)
				{	//hAdded not needed anymore
					hAdded.reset();
					hAdded = null;
				}
				_addedChanged =false
			}
			
			if ((highlightsInvalid || highlightsLocationInvalid) 
				&& (hSpelling != null || hAdded != null || hDeleted!= null))
			{ 	//if atleast one highlighter is working activate the eventhandlers
				if (!isInitialized)
				{
					this.addEventListener(KeyboardEvent.KEY_UP,key_Handler,false,3,true);
					this.addEventListener(KeyboardEvent.KEY_DOWN,key_Handler,false,3,true); //clear spellinghighlights, accept , reject current change.
					this.addEventListener(Event.CHANGE,change_Handler,false,3,true); //immediate update
					this.addEventListener(ScrollEvent.SCROLL,invalidate_Handler,false,3,true);//periodical update
					this.addEventListener(ResizeEvent.RESIZE,invalidate_Handler,false,3,true);//periodical update
				  	createDrawingCanvas();
					createValidator();
					isInitialized = true;
				}
				 //first time we redraw then with every text change we refresh
				if (highlightsLocationInvalid)
				 	refresh();
				else if (highlightsInvalid)
					draw();
				highlightsLocationInvalid =false;
				highlightsInvalid = false;
			}
			else if (hSpelling == null && hAdded == null && hDeleted== null && isInitialized)
			{
 				this.removeEventListener(KeyboardEvent.KEY_UP,key_Handler,false); 
				this.removeEventListener(KeyboardEvent.KEY_DOWN,key_Handler,false); //clear spellinghighlights, accept , reject current change.
				this.removeEventListener(Event.CHANGE,change_Handler,false); //immediate update
			    this.removeEventListener(ScrollEvent.SCROLL,invalidate_Handler,false);//periodical update
                this.removeEventListener(ResizeEvent.RESIZE,invalidate_Handler,false);//periodical update
              	destroyDrawingCanvas();
				destroyValidator();
				isInitialized = false;
			}
			  
		    
		}
		/**add the canvas on which all highlighter can draw**/
		protected override function createChildren():void
		{
			super.createChildren(); 
			this.textField.alwaysShowSelection = true;
			this.textField.selectable =true;
			// this.textField.autoSize = TextFieldAutoSize.LEFT;
		}
		
		public function invalidateHighlights():void
		{
			//if (this.id != null  && this.id.indexOf("1") > 0)
				//trace ("invalidate Highlights " , "\n", this.htmlText ); 
		    highlightsInvalid = true;
		    invalidateProperties();
		}
		
		public function invalidateHighlightsLocations():void
		{
			//if (this.id != null  && this.id.indexOf("1") > 0)
				//trace ("invalidate HighlightsLocations " , "\n", this.htmlText ); 
		    highlightsLocationInvalid = true;
		    invalidateProperties();
		}
	/*	public override function invalidateProperties():void
		{  
			if (this.id != null && this.htmlText!= null && this.id.indexOf("1") > 0)
					trace("invalidateProperties 1" , this.htmlText.substr(115));
			super.invalidateProperties();
			if (this.id != null && this.htmlText!= null && this.id.indexOf("1") > 0)
					trace("invalidateProperties 2" , this.htmlText.substr(115));
		
		}  */
		
		/**
        * Erases all highlights from the bitmap canvas and fires a redraw event
        */
		private function draw():void
		{ /*   if (this.id != null  && this.id.indexOf("1") > 0)
				//trace ("draw  "  , "\n", this.htmlText );    */
        	//clear drawn graphics 
			if (highlightsCanvas!=null)
				highlightsCanvas.graphics.clear();   
        	this.highlightsCanvas.x = this.textField.x;
        	this.highlightsCanvas.y = this.textField.y;
			this.dispatchEvent(new Event(DRAW_EVENT,true,false));
			  
        }
         
        /**
        * similar to redraw however this refresh fires first a refresh handler causing all highlighters to relocate their ranges
        */ 
        private function refresh():void
		{   /* if (this.id != null  && this.id.indexOf("1") > 0)
				trace ("refresh  "  , "\n", this.htmlText );   */
			//trace ("CustomTextArea: refresh") clear drawn graphics 
			 if (highlightsCanvas!=null)
				highlightsCanvas.graphics.clear();   
			this.highlightsCanvas.x = this.textField.x;
			this.highlightsCanvas.y = this.textField.y; 
        	this.dispatchEvent(new Event(REFRESH_EVENT,true,false));
        } 
        
	 
		/***********************************Event Listeners *************************/ 
		/** fired when the user attempts to change textfield content. 
		 * The purpose is to prevent user change overlappign with hightlights
		 * so if a key is pressed inside a tracked change
		 * we accept that change if it is added
		 *  or reject it if it is deleted. */ 
		private function key_Handler(evt:KeyboardEvent):void
		{   //trace ("key_Handler" , evt.charCode );
			if (evt.charCode == 0) //not a key
			  return;
			 //trace("CustomTextArea: keyUp_Handler" ,evt);;
			/*  if (this.textField.text == null || this.textField.text.length ==0) 
        	 {
	        	wrongWords = null;
				addedIndexRanges = null;
				deletedIndexRanges = null;
				invalidateHighlights();
				return ; 
        	 }	   */ 
			 //get the caret index.
			  var range:Range ; 
			  var found:Boolean =false;
			  var index:int = this.textField.caretIndex;  
			  if (hDeleted != null)			 
			  { 
				  var deletedRanges:Array = hDeleted.ranges;
				  // if this caret index is included in a deletion, reject this deletion
				  for (var i:int=0;i<deletedRanges.length;i++)
				  { 
				  	 range = deletedRanges[i];
				  	 if (range.beginIndex <= index && range.endIndex >= index)
				  	 {
				  	    found = true;
				  	    break; 
				  	 }
				  }
				  if (found)//rejecting the range
				  {
				  	currentSelection = range;
				  	currentSelectionType = DELETE;
				  	rejectCurrentChange(false);
				  }
			  }
			  
			  if (!found && hAdded!=null)
			  { 	
			  		//if this caret index is included in an addition
				  	var addedRanges:Array = hAdded.ranges;
				    for (var j:int=0;j<addedRanges.length;j++)
					{ 
					  	 range = addedRanges[j];
					  	 if (range.beginIndex <= index && range.endIndex >= index)
					  	 {
					  	    found = true;
					  	    break; 
					  	 }
					}
					if (found)//accept this addition
					{
					  	currentSelection = range;
					  	currentSelectionType = ADD;
					  	acceptCurrentChange(false);
					}  
			  }		
			   
  			  if (found) 
  			     invalidateHighlightsLocations();
  			  
		}  
		
		private function change_Handler(evt:Event):void
		{    /*  trace("CustomTextArea: change_Handler" ,evt); */
			 if (this.textField.text == null || this.textField.text.length ==0) 
        	 {
	        	wrongWords = null;
				addedIndexRanges = null;
				deletedIndexRanges = null;
				invalidateHighlights();
				return ; 
        	 }	 
        	 else
        	 {
        	   invalidateHighlightsLocations();
        	 }
            
		}
		/**
                  updates the size whenever a change event or backspace or delete occurs
                */
		private function  resize_Handler(evt:Event):void
		{
			//this is to fix issue with change handle not catching backspace or delete key.
			if (evt is KeyboardEvent &&  KeyboardEvent(evt).keyCode != Keyboard.BACKSPACE && KeyboardEvent(evt).keyCode != Keyboard.DELETE)
			  return ; 
            invalidateSize();
            invalidateHighlights();
        }
              
        /**
        * Fired when the the user scrolls , resizes
        * if called without an event like in the case of input change postiions will be recalculated. 
        */  
        private function invalidate_Handler(evt:Event):void
        {  
			//trace("invalidate..." , evt);  
          if (!validateTimer.running) 
          {	
          	validateTimer.start(); 
          }
         
        }
		 
		 /**Fired periodically to validate the input field in case of scrolling or resizing*/
        private function validate_Handler(evt:TimerEvent):void
        {   
			//trace("validating..." , evt);  
           validateTimer.reset();
 		   invalidateHighlights();
        } 
        
		 
		/**
		 * Selects a range, and sets it as the current selection.
		 * */
		private function setCurrentSelection(currentSelection:Range = null,currentSelectionType:int = NONE):void
		{
		   if (currentSelectionType!= NONE)
		   		this.currentSelection = currentSelection;
		   else
		      this.currentSelection = null;
		   this.currentSelectionType = currentSelectionType;
		   if (currentSelection != null )
		   {
				if (this.textField.maxScrollV > 1)
		      	{	var line:int = this.textField.getLineIndexOfChar(currentSelection.beginIndex);
					this.textField.scrollV = line+1;
		      	} 
				this.setFocus();
			//	trace ("caret at", this.textField.caretIndex, "set Selection ",currentSelection.beginIndex, currentSelection.endIndex);
		  		this.textField.setSelection(currentSelection.beginIndex,currentSelection.endIndex);
			
		   }
		   
		    
		    
		}
		/********************************initializing and destruction functions *******************************/
		  /**
         * creates the validator that runs periodically to validate the positions of the highligihts
         */
        private function createValidator():void
        {   if (validateTimer != null ) return;
			//trace ("CustomTextArea createValidator Timer");
      		validateTimer = new Timer(4,1); 
			validateTimer.addEventListener(TimerEvent.TIMER,validate_Handler,false,0,true);
        }
        
        private function createDrawingCanvas():void
        {  
           if (highlightsCanvas != null) return; 
			//trace ("CustomTextArea createDrawingCanvas");
			//create sprite that hold striking and highlighting effects 
			this.highlightsCanvas = new Sprite(); 
			
            var ind:int = this.getChildIndex(DisplayObject(textField));
            this.addChildAt(highlightsCanvas,ind);
        }
        
         private function destroyValidator():void
        {   if (validateTimer == null ) return;
			//trace ("CustomTextArea destroyValidator Timer");
			validateTimer.removeEventListener(TimerEvent.TIMER,validate_Handler,false);
			validateTimer = null;
        }
        
        private function destroyDrawingCanvas():void
        {  
           if (highlightsCanvas == null) return; 
			//trace ("CustomTextArea destroyDrawingCanvas");
		   //create bitmap that hold striking and highlighting effects 
         /* 	var ind:int = this.getChildIndex(DisplayObject(textField));
            if (this.getChildAt(ind-1) is Bitmap)
            	this.removeChildAt(ind-1);
         	this.highlightsCanvas = null */
         	if (highlightsCanvas.parent != null) {
				highlightsCanvas.parent.removeChild(highlightsCanvas);
				highlightsCanvas = null;
			} 
        }
		
		/************************************Public Utitlity Methods**************************/
	
		/**
		 * selects the next change tracked
		 */ 
		public function goToNextChange(reset:Boolean = false):Boolean
		{	
 
			if (highlightsLocationInvalid) refresh();
			var startingIndex:int;
			if (reset) 
				startingIndex = 0;
		    else if (currentSelection ==null || currentSelectionType == NONE )
		        startingIndex = textField.caretIndex;
		    else 
		        startingIndex =   Math.max(currentSelection.beginIndex,currentSelection.endIndex);
		    
			var rangeAdded:Range =  (hAdded!=null ? hAdded.getNext(false,startingIndex): null); 
			var rangeDeleted:Range = (hDeleted!= null ? hDeleted.getNext(false,startingIndex):null);
			if (rangeAdded == null && rangeDeleted == null)
			{   //unselect and set the current index at the last character
				setCurrentSelection(new Range(this.textField.text.length-1,this.textField.text.length-1));
				 
			}    
			else if (rangeAdded != null && (rangeDeleted == null || (rangeDeleted.beginIndex > rangeAdded.beginIndex)))
			     setCurrentSelection(rangeAdded,ADD);
			else if ( rangeDeleted != null && (rangeAdded == null || (rangeDeleted.beginIndex < rangeAdded.beginIndex)) )
			     setCurrentSelection(rangeDeleted,DELETE);
 
			return  (currentSelectionType != NONE); // something was selected or not 
			   
		}
		/**
		 * selects the prev change tracked
		 */ 
		public function goToPrevChange(reset:Boolean = false):Boolean
		{
			if (highlightsLocationInvalid) refresh();
			var startingIndex:int ;
			if (reset) 
				startingIndex = this.textField.text.length ;
		    else if (currentSelection ==null || currentSelectionType == NONE )
		        startingIndex = textField.caretIndex;
		    else 
		        startingIndex =  Math.min(currentSelection.beginIndex,currentSelection.endIndex);
		     
 				
			var rangeAdded:Range =  (hAdded!=null ? hAdded.getPrevious(false,startingIndex): null); 
			var rangeDeleted:Range = (hDeleted!= null ? hDeleted.getPrevious(false,startingIndex):null);
  			if (rangeAdded == null && rangeDeleted == null)
  		    {   //unselect and set the current index at the first character
  		        setCurrentSelection(new Range(0,0) );  
			 
  		    }
			else if (rangeAdded != null && (rangeDeleted == null || (rangeDeleted.beginIndex < rangeAdded.beginIndex)))
			    setCurrentSelection(rangeAdded,ADD);
		    else if ( rangeDeleted != null && (rangeAdded == null || (rangeDeleted.beginIndex > rangeAdded.beginIndex)) )
			    setCurrentSelection(rangeDeleted,DELETE);
			return  (currentSelectionType != NONE); // something was selected  or not
		}
		
		/**
		 * accepts the current selected change, if deleted the select is removed from the text
		 */ 
		public function acceptCurrentChange(resetSelection:Boolean=true):void
		{  
			//trace ("acceptCurrentChange");
		   if (currentSelection == null || currentSelectionType == NONE)
		   {
				return ; 
		   	   
		   }
		   this.setFocus();
		   if (currentSelectionType == ADD)
			{
		     hAdded.removeHighlightedRange(currentSelection);
				invalidateHighlightsLocations();
			}
		   else if (currentSelectionType == DELETE)
		   { 
			   hDeleted.removeHighlightedRange(currentSelection,true);	
			   invalidateHighlightsLocations();
		   }
		   if (resetSelection)
		   {
		   		 
		   		setCurrentSelection(new Range(currentSelection.beginIndex,currentSelection.beginIndex));
		   }
		   else
		   		setCurrentSelection();
		    
		}
		
		/**
		 * rejects the current selected change, if added the selection is removed from the text
		 */
		public function rejectCurrentChange(resetSelection:Boolean=true):void
		{
		   if (currentSelection == null || currentSelectionType == NONE)
		   { 
		      return ;//goToNextChange();
		       
		   } 
		   this.setFocus();
		   if (currentSelectionType == ADD)
		   { 
				hAdded.removeHighlightedRange(currentSelection,true);
				
			  invalidateHighlightsLocations();
		   }
		   else if (currentSelectionType == DELETE)
		   { 
		   	   hDeleted.removeHighlightedRange(currentSelection); 
				invalidateHighlightsLocations();
		   }
		   if (resetSelection)
		   {
		   		 
		   		setCurrentSelection(new Range(currentSelection.beginIndex,currentSelection.beginIndex));
		   }
		   else
		   		setCurrentSelection();
		}
		/**
		 * accepts all changes *
		 */ 
		public function acceptAllChanges():void
		{
			 if (hAdded !=null)
			 {
				addedIndexRanges = null;
			 }
			 if (hDeleted != null)
			 {
				 var ranges:Array = hDeleted.ranges; 
				 var maxRange:Range ;
				 var maxRangeIndex:int =-1  ;
				 //loop no the ranges and start deleting from the max range
				 while( ranges.length > 0)
				 {//find max 
				   for (var i:int=0;i<ranges.length;i++)
				   {    
				   	    if (maxRange == null || maxRange.beginIndex < Range(ranges[i]).beginIndex)
				   	    {   maxRange = Range(ranges[i]);
				   	        maxRangeIndex = i;
				   	    }
				   }
				   //remove the range
				    textField.replaceText(maxRange.beginIndex, maxRange.endIndex,"");
				    ranges.splice(maxRangeIndex,1);
				    maxRange =null;
				    maxRangeIndex = -1;
				 } 
				 deletedIndexRanges  = null;
			 }
			 invalidateHighlightsLocations();  
		}
		/**
		 * rejects all changes 
		 */  
		public function rejectAllChanges():void
		{     
			 if (hDeleted != null)
			 { 
				deletedIndexRanges = null;
				
			 }
			 if (hAdded != null)
			 {
				 var ranges:Array = hAdded.ranges;
		 		 var maxRange:Range ;
				 //loop on the ranges and start deleting from the max range
				 while( ranges.length > 0)
				 {//find max 
				   for (var i:int=0;i<ranges.length;i++)
				   {
				   	    if (maxRange == null || maxRange.beginIndex > Range(ranges[i]).beginIndex)
				   	       maxRange = Range(ranges[i]);
				   }
				   //remove the range
				    textField.replaceText(maxRange.beginIndex, maxRange.endIndex,""); 
				    maxRange =null;
				 }
 				 addedIndexRanges = null;
			 }
			 invalidateHighlightsLocations();
		}
		 //this allows user to skip highlighted ranges when navigating by clicking beyond or before the ranges 
		 private function  mouseClick_Handler (e:MouseEvent):void
		{   this.setFocus();
		   if (currentSelection!=null || currentSelectionType != NONE)
		     setCurrentSelection(); 
		 }
		
		
		/****************************context menu stuff stuff ****************/
		private var contextData:Object; // stores temporary data during a right click.
		private var menuItems:Dictionary; // stores references to custom menuItems
		private var contextM:ContextMenu;
		
		/**
		* Locale string used to generate context menu options. %WORD% will be
		* replaced with the related word.
		**/
		private static var str_no_suggestions:String="No suggestions for: %WORD%";
		
		private static var str_Ignore:String="Ignore";

		private static var str_IgnoreAll:String="IgnoreAll";
		 
		
		/**
		 * creates the context menu and adds listener if one doesn't exit
		 */  
		private function setupContext():void
		{   if (contextM != null) return; 
			if (this.contextMenu  == null)
        	{
				contextM = new ContextMenu();
				this.contextMenu = contextM;
        	}
			contextM = this.contextMenu; 
        	contextM.hideBuiltInItems();
			contextM.addEventListener(ContextMenuEvent.MENU_SELECT,buildContextMenu,false,10,true);
			//add the ignoreall event listener on the application level
			parentApplication.addEventListener(CustomTextArea.IGNORE_WORDS_EVENT,handleIgnoreAll,false,0,true);                 
			  
		}
		
		/***
		 *Destroys and cleans the context menu 
		 */  
		private function destroyContext():void
		{   if (contextM == null) return; 
			clearContextMenu();
           	contextM.removeEventListener(ContextMenuEvent.MENU_SELECT,buildContextMenu,false);
			contextM = null;
			//remove the ignoreall event listener on the application level
			parentApplication.removeEventListener(CustomTextArea.IGNORE_WORDS_EVENT,handleIgnoreAll,false);                 
			    
		}  
		 
		/**
		 * clears the context menu from added items
		 */ 
		private function clearContextMenu():void {
			if (menuItems == null || contextM== null)return; 
			  
			var customItems:Array = contextM.customItems; 
			//contextM.customItems = customItems.splice(0,customItems.length);
			 for (var i:uint=customItems.length; i>0; i--) {
				if (menuItems[customItems[i-1]]) {
					customItems.splice(i-1,1);
				}
			} 
			contextM.customItems = customItems;
			//textField.contextMenu = cm;
			menuItems = null;
		}
		/**
		 * builds a context menu of spelling suggesstions for the currently selected word
		 */ 
		private function buildContextMenu(evt:ContextMenuEvent):void 
		{   // remove old custom items:
			clearContextMenu();
			if (contextM == null || _wrongWords == null) return; 
			
		 
			 	// run through all text:
			var wc:Array = CharacterSet.wordCharSet;
			var ic:Array = CharacterSet.innerWordCharSet;
			var xc:Array = CharacterSet.invalidWordCharSet;
			
			var customItems:Array = contextM.customItems;
			
			// first we need to find the word that was clicked:
			var txt:String = textField.text;
			var charIndex:int = textField.getCharIndexAtPoint(textField.mouseX+textField.scrollH,textField.mouseY);
			
			
			/*new method 
			  find spelling boundary surrounding this index.
			*/
			var range:Range = hSpelling.getRange(charIndex);
			if (range==null)
			 return;
			var word:String = txt.substring(range.beginIndex,range.endIndex);
			var newItems:Array = [];
			contextData = {beginIndex:range.beginIndex,endIndex:range.endIndex,word:word};
			//add context menu items.
			    var suggestions:Array = _wrongWords[word];
			    if (suggestions != null && suggestions.length > 0 )
				{	
					//newItems = [new ContextMenuItem(str_select_a_suggestion,false,false)];
					for (var i:int=0; i<suggestions.length; i++) 
					{
						var suggestion:String = suggestions[i];
						var suggestionCMItem:ContextMenuItem = new ContextMenuItem(suggestion,false,this.editable);
						suggestionCMItem.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT,handleSuggestionSelect,false,0,true);
						newItems.push(suggestionCMItem);
					}
					
					var addCMItem:ContextMenuItem = new ContextMenuItem(str_Ignore,true,this.editable);
					 
				   addCMItem.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT,handleIgnoreWord,false,0,true);
				   newItems.push(addCMItem);
		
				   addCMItem = new ContextMenuItem(str_IgnoreAll,false,this.editable);
				   addCMItem.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT,handleIgnoreAllWord,false,0,true);
				   newItems.push(addCMItem);
				}
	
			// push in the new items:
			if (newItems.length > 0 )
			{
				menuItems = new Dictionary(true);
				for (i=0; i<newItems.length; i++) {
					var item:ContextMenuItem = newItems[i] as ContextMenuItem;
					if (i == 0 && customItems.length > 0) { item.separatorBefore = true; }
					customItems.push(item);
					menuItems[item] = true;
				}
			}
		 
		}
		/**
		 *Handles replacing a word with the currently selected context menu option */
		private function handleSuggestionSelect(evt:ContextMenuEvent):void {
			 
			textField.replaceText(contextData.beginIndex, contextData.endIndex, evt.target.caption);
			textField.setSelection(contextData.beginIndex,contextData.beginIndex+evt.target.caption.length);
			var evtChange:Event = new Event(Event.CHANGE,true,false);
			textField.dispatchEvent(evtChange);
		 
		}

		/**
		 *Handles replacing a word with the currently selected context menu option */
		private function handleIgnoreWord(evt:ContextMenuEvent):void {
		   var word:String = contextData.word
		   /* need a way to mark word as ignored and remove the marking when the word is edited */
		   hSpelling.ignoreHighlightedRange(new Range(contextData.beginIndex, contextData.endIndex));
 
		 }
		
		/**
		 *Handles replacing a word with the currently selected context menu option */
			private function handleIgnoreAllWord(evt:ContextMenuEvent):void 
			{
				 var word:String = contextData.word;
		         var evtIgnore:Event = new Event(CustomTextArea.IGNORE_WORDS_EVENT,true,false);
		         ignoredWords.push(word);
		         _ignoredWordsChanged = true;
		         parentApplication.dispatchEvent(evtIgnore);
		 	}
		 	
		 	private function handleIgnoreAll(evt:Event):void
		 	{
		 		 _ignoredWordsChanged = true;
		 		 invalidateHighlights();
		 	} 
		 	 
		/********************end of context stuff ***********************************************/
		
		/********************resize to fit content ************************************/
		//resizes this box between minHeight and maxHeight
		override protected function measure():void
		{
			super.measure();
			 
		    const MARGIN:int = 7;
			var maxH:int = this.explicitMaxHeight;
			var minH:int = this.explicitMinHeight; 
			//measuredMinWidth = DEFAULT_MEASURED_MIN_WIDTH;
			//measuredWidth = DEFAULT_MEASURED_WIDTH;
			// TextArea is minimum of two lines of text
			 measuredMinHeight =  DEFAULT_MEASURED_MIN_HEIGHT;
	   
			if (_autoSize)
		    {
				var lineHeight : uint = MARGIN;
				if (isNaN(minH) || minH == 0)
			  		minH = DEFAULT_MEASURED_MIN_HEIGHT
				/* for (var i : int = 0; i < textField.numLines; i++)
			{
					lineHeight += textField.getLineMetrics(i).height;
		} */
				lineHeight += textField.textHeight;
				lineHeight = Math.max(lineHeight,minH);
				if (!isNaN(maxH) && maxH != 0)
					lineHeight = Math.min(maxH,lineHeight);

				this.measuredHeight = lineHeight;
			} 
		}
     }
}


